/*
 * Copyright (C) Michael Opitz
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA
 */
#ifndef DOM_H
#define DOM_H

#include <iksemel.h>
#include <boost/shared_ptr.hpp>
#include <string>

using namespace std;

namespace model {
	template <class policy>
	class parser;
	class dom_policy;
}

/**
 * \brief This namespace contains all model-classes.
 * \author Michael Opitz
 */
namespace model {
	/**
	 * This class provides a DOM-interface for viewing and editing
	 * xml-files.
	 * \author Michael Opitz <opitz.michael@gmail.com>
	 * \version 0.1
	 */
	class dom {
	public:
		/**
		 * This enum descripes the type of this node, which can either
		 * be a tag-node, a data-node, or a attribute.
		 */
		enum node_type {TAG, CDATA, ATTRIBUTE};
		typedef boost::shared_ptr<dom> ptr;

		template <class policy>
		friend class parser;
		friend class dom_policy;

		/**
		 * The standard-constructor initialises a empty node.
		 */
		dom();
		/**
		 * This constructor parses the xml-information, which is
		 * included in the string and stores it into this object.
		 * \param name is an xml-string, which should be parsed.
		 */
		dom(const string &name);
		/**
		 * This is the copy-constructor, which makes a deep-copy of
		 * the dom-information.
		 */
		dom(const dom &d);
		/**
		 * The destructor frees the allocated memory.
		 */
		~dom();

		/**
		 * The assign-operator makes a deep-copy of the dom-information.
		 */
		dom& operator = (const dom &d);
		
		/**
		 * This method returns a pointer of the next element in the
		 * dom-tree
		 * \return A pointer to a dom-object is returned.
		 */
		const ptr next() const {return static_cast<const ptr>(
				const_cast<dom*>(this)->next());}
		/**
		 * This method returns a pointer of the next element in the
		 * dom-tree
		 * \return A pointer to a dom-object is returned.
		 */
		ptr next();

		/**
		 * This method returns a pointer of the previouse element in
		 * the dom-tree.
		 * \return A pointer to a dom-object is returned.
		 */
		const ptr prev() const {return static_cast<const ptr>(
				const_cast<dom*>(this)->prev());}
		/**
		 * This method returns a pointer of the previouse element in
		 * the dom-tree.
		 * \return A pointer to a dom-object is returned.
		 */
		ptr prev();

		/**
		 * This method retunrs a pointer to the parent-element in the 
		 * dom-tree.
		 * \return A pointer to a dom-object is returned.
		 */
		const ptr parent() const {return static_cast<const ptr>(
				const_cast<dom*>(this)->parent());}
		/**
		 * This method retunrs a pointer to the parent-element in the 
		 * dom-tree.
		 * \return A pointer to a dom-object is returned.
		 */
		ptr parent();

		/**
		 * This method returns a pointer to the child-element of this
		 * node.
		 * \return A pointer to a dom-object is returned.
		 */
		const ptr child() const {return static_cast<const ptr>(
				const_cast<dom*>(this)->child());}
		/**
		 * This method returns a pointer to the child-element of this
		 * node.
		 * \return A pointer to a dom-object is returned.
		 */
		ptr child();

		/**
		 * This method returns a pointer to the attributes of the 
		 * current node.
		 * \return A pointer to a dom-object is returned.
		 */
		const ptr attrib() const {return static_cast<const ptr>(
				const_cast<dom*>(this)->attrib());}
		/**
		 * This method returns a pointer to the attributes of the 
		 * current node.
		 * \return A pointer to a dom-object is returned.
		 */
		ptr attrib();

		/**
		 * This method returns a pointer to the root-element of the
		 * current dom-tree.
		 * \return A pointer to a dom-object is returned.
		 */
		const ptr root() const {return static_cast<const ptr>(
				const_cast<dom*>(this)->root());}
		/**
		 * This method returns a pointer to the root-element of the
		 * current dom-tree.
		 * \return A pointer to a dom-object is returned.
		 */
		ptr root();

		/**
		 * This method returns a pointer to the next tag-element of 
		 * the dom-tree. Only tag nodes are considered and other types
		 * or the nodes are skipped.
		 * \return A pointer to a dom-object is returned.
		 */
		const ptr next_tag() const {return static_cast<const ptr>(
				const_cast<dom*>(this)->next_tag());}
		/**
		 * This method returns a pointer to the next tag-element of 
		 * the dom-tree. Only tag nodes are considered and other types
		 * of the nodes are skipped.
		 * \return A pointer to a dom-object is returned.
		 */
		ptr next_tag();

		/**
		 * This method returns a pointer to the previouse tag-element 
		 * of the dom-tree. Only tag nodes are considered and other 
		 * types of the nodes are skipped.
		 * \return A pointer to a dom-object is returned.
		 */
		const ptr prev_tag() const {return static_cast<const ptr>(
				const_cast<dom*>(this)->prev_tag());}
		/**
		 * This method returns a pointer to the previouse tag-element 
		 * of the dom-tree. Only tag nodes are considered and other 
		 * types of the nodes are skipped.
		 * \return A pointer to a dom-object is returned.
		 */
		ptr prev_tag();
		
		/**
		 * This method returns a pointer to the first tag of the 
		 * dom-tree. Only tag nodes are considered and other 
		 * types of the nodes are skipped.
		 * \return A pointer to a dom-object is returned.
		 */
		const ptr first_tag() const {return static_cast<const ptr>(
				const_cast<dom*>(this)->first_tag());}
		/**
		 * This method returns a pointer to the first tag of the 
		 * dom-tree. Only tag nodes are considered and other 
		 * types of the nodes are skipped.
		 * \return A pointer to a dom-object is returned.
		 */
		ptr first_tag();

		/**
		 * This method returns the type of the node.
		 * \return The type of the node is returned.
		 */
		node_type type() const;
		/**
		 * This method returns the name of the tag for nodes with
		 * the type TAG.
		 * \return A string with the name is returned.
		 */
		string name() const;
		/**
		 * This method returns the node's data.
		 * \return A string is returned by this method. If there is no
		 * data in the string, the method returns an empty string.
		 */
		string cdata() const;
		/**
		 * This method returns true if the node has child-nodes.
		 * \return This method returns true if the node has child-nodes.
		 */
		bool has_children() const;
		/**
		 * This method returns true if there are any attributes in the
		 * node.
		 * \return True is returned if there are any attributes in the
		 * node.
		 */
		bool has_attribs() const;

		/**
		 * This method searches the dom-tree for TAG-nodes, which names
		 * are matched by the passed parameter. 
		 * \return If there exists a matching TAG-node within the tree
		 * a pointer to the node will be returned. If there are no 
		 * TAG-nodes a pointer to an empty dom-object will be returned.
		 */
		const ptr find(const string &name) const {return static_cast
			<const ptr>(const_cast<dom*>(this)->find(name));}
		/**
		 * This method searches the dom-tree for TAG-nodes
		 * \param name is the search pattern 
		 * \return If there exists a matching TAG-node within the tree
		 * a pointer to the node will be returned. If there are no 
		 * TAG-nodes a pointer to an empty dom-object will be returned.
		 */
		ptr find(const string &name);

		/**
		 * This method searches the dom-tree for CDATA-nodes. 
		 * \param name is the search pattern.
		 * \return If there was a matching CDATA-node found within the
		 * tree, the CDATA will be returned within a string. If not
		 * there will be an empty string returned.
		 */
		const string find_cdata(const string &name) const {return 
			static_cast <const string>
				(const_cast<dom*>(this)->find_cdata(name));}
		/**
		 * This method searches the dom-tree for CDATA-nodes. 
		 * \param name is the search pattern.
		 * \return If there was a matching CDATA-node found within the
		 * tree, the CDATA will be returned within a string. If not
		 * there will be an empty string returned.
		 */
		string find_cdata(const string &name);

		/**
		 * This method searches the dom-tree for an attribute.
		 * \param name is the attribute name. 
		 * \return If the attribute is found, the value is returned, if
		 * not an empty string is returned.
		 */
		const string find_attrib(const string &name) const {return 
			static_cast <const string>
				(const_cast<dom*>(this)->find_attrib(name));}
		/**
		 * This method searches the dom-tree for an attribute.
		 * \param name is the attribute name. 
		 * \return If the attribute is found, the value is returned, if
		 * not an empty string is returned.
		 */
		string find_attrib(const string &name);

		/**
		 * This method searches the dom-tree for a node.
		 * \param tagname is the tagname of the node.
		 * \param attrname is the name of an node's attribute.
		 * \param val is the value of the node's attribute.
		 * \return If the node is found a pointer to the node is 
		 * returned, otherwise a pointer to an empty node is returned.
		 */
		const ptr find_with_attrib(const string &tagname, 
			const string &attrname, const string& val) const
		{
			return static_cast<const ptr>(const_cast<dom*>
				(this)->find_with_attrib(tagname, attrname, 
							 val));
		}
		/**
		 * This method searches the dom-tree for a node.
		 * \param tagname is the tagname of the node.
		 * \param attrname is the name of an node's attribute.
		 * \param val is the value of the node's attribute.
		 * \return If the node is found a pointer to the node is 
		 * returned, otherwise a pointer to an empty node is returned.
		 */
		ptr find_with_attrib(const string &tagname, 
			const string &attrname, const string& val);

		/**
		 * This method inserts a TAG-type node with the passed tag-name.
		 * \param name is the name of the new tag, which will be created
		 * \return The node-pointer is returned. 
		 */
		ptr insert(const string &name);
		/**
		 * This method inserts a CDATA-type node.
		 * \param name is the data, which should be inserted in the node
		 * \return A pointer to the node will be returned.
		 */
		ptr insert_cdata(const string &name);
		/**
		 * This method inserts a new attribute with value in the node.
		 * \param name ist the name of the attribute.
		 * \param val is the value of the attribute.
		 * \return A pointer to the dom-node is be returned.
		 */
		ptr insert_attrib(const string &name, const string &val);
		/**
		 * This method inserts a dom-tree. The nodes are not,
		 * deep-copied, so that you must be carefully.
		 * \param node is the node which should be inserted.
		 * \return A pointer to the node is be returned.
		 */
		ptr insert_node(const dom &node);
		/**
		 * Changes the links of the other nodes so that this node
		 * becomes invisible.
		 */
		void hide();

		/**
		 * This method checks if the dom-object is empty. Empty
		 * nodes are returned by methods like next(); if there are no
		 * following elements in the dom-tree. 
		 */
		operator bool() const;

		/**
		 * Converts the dom-tree into a string.
		 * \return A string is returned.
		 */
		string to_string() const;

		/**
		 * Stores the dom-tree into a file.
		 * \param filename is the file, in which the tree should be
		 * stored.
		 */
		bool save(const string &filename) const;
		/**
		 * Loads the dom-tree from a file.
		 * \param filename is the file, from which the tree should be
		 * loaded.
		 */
		bool load(const string &filename);

		void set_managed(bool managed) 
		{ is_managed = managed; }
	private:
		/**
		 * This method constructs the dom-object from the underlying 
		 * iks* object.
		 * \param d is the iks*-node from which the dom-node is 
		 * created.
		 * \param managed must be false, if the object muss delete the
		 * allocated storage by itself
		 */
		dom(iks *d, bool managed = true):is_managed(managed), node(d) {}

		/**
		 * This method returns the underlying iks* object.
		 * \return A iks*-object will be returned.
		 */
		iks* get() {return node;}
		/**
		 * This method returns the underlying iks* object.
		 * \return A iks*-object will be returned.
		 */
		const iks* get() const {return node;}

		/**
		 * This method is an implementing-detail
		 */
		ptr get_empty_ptr();
		/**
		 * This method is an implementing-detail
		 */
		ptr get_ptr(iks *node);

		/**
		 * This flag is set if the dom-node is managed and shouldn't
		 * be freed.
		 */
		bool is_managed;

		/**
		 * This attribute stores the original iks-node-object. It is 
		 * mutable because the iksemel library doesn't support const-
		 * correctness
		 */
		mutable iks *node;	// dirty hack
	};
}

#endif

